// -----------------------------
// Author: 		Dhaenens Gert 
// Creation: 	8/6/2013 10:10:29 PM				
// -----------------------------

#ifndef _LuaDataTypeInl_h
#define _LuaDataTypeInl_h

#include "Lua.h"
#include "LuaStack.h"
#include "LuaClassRegister.h"

namespace ObjectLua
{

	/*
	//	Push this data type onto the stack
	*/
	template<typename T>
	void LuaDataType<T>::push(Lua* lpLua, T data)
	{
		LuaGCObject<T>* gcObject = reinterpret_cast<LuaGCObject<T>*>(lpLua->getStack()->requestUserData(sizeof(LuaGCObject<T>)));
		gcObject->Object = new T(data);
		gcObject->ShouldBeCollected = true;

		if(LuaClassRegister<T>::isRegistered())
		{
			LuaTable* lpMetaTable = LuaClassRegister<T>::getMetaTable();
			lpMetaTable->push();
			lpLua->getStack()->setMetaTable(-2);
		}
	}
	/*
	//	Get this data type from the stack
	*/
	template<typename T>
	T LuaDataType<T>::get(Lua* lpLua, int idx)
	{
		if(LuaClassRegister<T>::isRegistered())
			lpLua->getStack()->checkUserData(idx, LuaClassRegister<T>::getMetaTableName());
		LuaGCObject<T>* gcObject = reinterpret_cast<LuaGCObject<T>*>((lpLua->getStack()->getUserData(idx)));
		return *(gcObject->Object);
	}


	/*
	//	Template Specialisations
	*/

	/*
	//	Const values
	*/
	template<typename T>
	class OBJECTLUA LuaDataType<const T>
	{
	public:
		typedef const T	ArgType;

		/*
		//	Push this data type onto the stack
		*/
		static void	push(Lua* lpLua, const T data)
		{
			LuaGCObject<const T>* gcObject = reinterpret_cast<LuaGCObject<const T>*>(lpLua->getStack()->requestUserData(sizeof(LuaGCObject<const T>)));
			gcObject->Object = new T(data);
			gcObject->ShouldBeCollected = true;

			if(LuaClassRegister<T>::isRegistered())
			{
				LuaTable* lpMetaTable = LuaClassRegister<T>::getMetaTable();
				lpMetaTable->push();
				lpLua->getStack()->setMetaTable(-2);
			}
		}
		/*
		//	Get this data type from the stack
		*/
		static const T	get(Lua* lpLua, int idx)
		{
			if(LuaClassRegister<T>::isRegistered())
				lpLua->getStack()->checkUserData(idx, LuaClassRegister<T>::getMetaTableName());
			LuaGCObject<const T>* gcObject = reinterpret_cast<LuaGCObject<const T>*>((lpLua->getStack()->getUserData(idx)));
			return gcObject->Object;
		}
	};

	/*
	//	Pointers
	*/
	template<typename T>
	class OBJECTLUA LuaDataType<T*>
	{
	public:
		typedef T*	ArgType;

		/*
		//	Push this data type onto the stack
		*/
		static void	push(Lua* lpLua, T* data)
		{
			LuaGCObject<T>* gcObject = reinterpret_cast<LuaGCObject<T>*>(lpLua->getStack()->requestUserData(sizeof(LuaGCObject<T>)));
			gcObject->Object = data;
			gcObject->ShouldBeCollected = false;

			if(LuaClassRegister<T>::isRegistered())
			{
				LuaTable* lpMetaTable = LuaClassRegister<T>::getMetaTable();
				lpMetaTable->push();
				lpLua->getStack()->setMetaTable(-2);
			}
		}
		/*
		//	Get this data type from the stack
		*/
		static T*	get(Lua* lpLua, int idx)
		{
			// Else use regular system
			if(LuaClassRegister<T>::isRegistered())
				lpLua->getStack()->checkUserData(idx, LuaClassRegister<T>::getMetaTableName());
			LuaGCObject<T>* gcObject = reinterpret_cast<LuaGCObject<T>*>((lpLua->getStack()->getUserData(idx)));
			return gcObject->Object;
		}
	};

	/*
	//	Const Pointers
	*/
	template<typename T>
	class OBJECTLUA LuaDataType<const T*>
	{
	public:
		typedef const T*	ArgType;

		/*
		//	Push this data type onto the stack
		*/
		static void	push(Lua* lpLua, const T* data)
		{
			LuaGCObject<const T>* gcObject = reinterpret_cast<LuaGCObject<const T>*>(lpLua->getStack()->requestUserData(sizeof(LuaGCObject<const T>)));
			gcObject->Object = data;
			gcObject->ShouldBeCollected = false;

			if(LuaClassRegister<T>::isRegistered())
			{
				LuaTable* lpMetaTable = LuaClassRegister<T>::getMetaTable();
				lpMetaTable->push();
				lpLua->getStack()->setMetaTable(-2);
			}
		}
		/*
		//	Get this data type from the stack
		*/
		static const T*	get(Lua* lpLua, int idx)
		{
			if(LuaClassRegister<T>::isRegistered())
				lpLua->getStack()->checkUserData(idx, LuaClassRegister<T>::getMetaTableName());
			LuaGCObject<T>* gcObject = reinterpret_cast<LuaGCObject<T>*>((lpLua->getStack()->getUserData(idx)));
			return gcObject->Object;
		}
	};

	/*
	//	Reference
	*/
	template<typename T>
	class OBJECTLUA LuaDataType<T&>
	{
	public:
		typedef T&	ArgType;
		/*
		//	Push this data type onto the stack
		*/
		static void	push(Lua* lpLua, T& data)
		{
			LuaGCObject<T>* gcObject = reinterpret_cast<LuaGCObject<T>*>(lpLua->getStack()->requestUserData(sizeof(LuaGCObject<T>)));
			gcObject->Object = &data;
			gcObject->ShouldBeCollected = false;

			if(LuaClassRegister<T>::isRegistered())
			{
				LuaTable* lpMetaTable = LuaClassRegister<T>::getMetaTable();
				lpMetaTable->push();
				lpLua->getStack()->setMetaTable(-2);
			}
		}
		/*
		//	Get this data type from the stack
		*/
		static T&	get(Lua* lpLua, int idx)
		{
			if(LuaClassRegister<T>::isRegistered())
				lpLua->getStack()->checkUserData(idx, LuaClassRegister<T>::getMetaTableName());
			LuaGCObject<T>* gcObject = reinterpret_cast<LuaGCObject<T>*>((lpLua->getStack()->getUserData(idx)));
			return *(gcObject->Object);
		}
	};

	/*
	//	Const Reference
	*/
	template<typename T>
	class OBJECTLUA LuaDataType<const T&>
	{
	public:
		typedef const T&	ArgType;
		/*
		//	Push this data type onto the stack
		*/
		static void	push(Lua* lpLua, const T& data)
		{
			LuaGCObject<T>* gcObject = reinterpret_cast<LuaGCObject<T>*>(lpLua->getStack()->requestUserData(sizeof(LuaGCObject<T>)));
			gcObject->Object = &data;
			gcObject->ShouldBeCollected = false;

			if(LuaClassRegister<T>::isRegistered())
			{
				LuaTable* lpMetaTable = LuaClassRegister<T>::getMetaTable();
				lpMetaTable->push();
				lpLua->getStack()->setMetaTable(-2);
			}
		}
		/*
		//	Get this data type from the stack
		*/
		static const T&	get(Lua* lpLua, int idx)
		{
			if(LuaClassRegister<T>::isRegistered())
				lpLua->getStack()->checkUserData(idx, LuaClassRegister<T>::getMetaTableName());
			LuaGCObject<T>* gcObject = reinterpret_cast<LuaGCObject<T>*>((lpLua->getStack()->getUserData(idx)));
			return *(gcObject->Object);
		}
	};

	/*
	//	Void
	*/
	template<>
	class OBJECTLUA LuaDataType<void>
	{
	public:
		typedef void		ArgType;
		/*
		//	Push this data type onto the stack
		*/
		static void	push(Lua* lpLua, void* data);
		/*
		//	Get this data type from the stack
		*/
		static void get(Lua* lpLua, int idx);
	};

	/*
	//	Integer
	*/
	template<>
	class OBJECTLUA LuaDataType<int>
	{
	public:
		typedef int		ArgType;
		/*
		//	Push this data type onto the stack
		*/
		static void	push(Lua* lpLua, int data);
		/*
		//	Get this data type from the stack
		*/
		static int	get(Lua* lpLua, int idx);
	};

	/*
	//	Unsigned Integer
	*/
	template<>
	class OBJECTLUA LuaDataType<unsigned int>
	{
	public:
		typedef unsigned int	ArgType;
		/*
		//	Push this data type onto the stack
		*/
		static void	push(Lua* lpLua, unsigned int data);
		/*
		//	Get this data type from the stack
		*/
		static unsigned int	get(Lua* lpLua, int idx);
	};


	/*
	//	Bool
	*/
	template<>
	class OBJECTLUA LuaDataType<bool>
	{
	public:
		typedef bool	ArgType;

		/*
		//	Push this data type onto the stack
		*/
		static void	push(Lua* lpLua, bool data);
		/*
		//	Get this data type from the stack
		*/
		static bool	get(Lua* lpLua, int idx);
	};


	/*
	//	Double
	*/
	template<>
	class OBJECTLUA LuaDataType<double>
	{
	public:
		typedef double	ArgType;
		/*
		//	Push this data type onto the stack
		*/
		static void	push(Lua* lpLua, double data);
		/*
		//	Get this data type from the stack
		*/
		static double	get(Lua* lpLua, int idx);
	};

	/*
	//	Float
	*/
	template<>
	class OBJECTLUA LuaDataType<float>
	{
	public:
		typedef float	ArgType;
		/*
		//	Push this data type onto the stack
		*/
		static void	push(Lua* lpLua, float data);
		/*
		//	Get this data type from the stack
		*/
		static float	get(Lua* lpLua, int idx);
	};

	/*
	//	char*
	*/
	template<>
	class OBJECTLUA LuaDataType<char*>
	{
	public:
		typedef char*	ArgType;
		/*
		//	Push this data type onto the stack
		*/
		static void	push(Lua* lpLua, char* data);
		/*
		//	Get this data type from the stack
		*/
		static char*	get(Lua* lpLua, int idx);
	};

	/*
	//	Integer
	*/
	template<>
	class OBJECTLUA LuaDataType<const int>
	{
	public:
		typedef const int	ArgType;
		/*
		//	Push this data type onto the stack
		*/
		static void			push(Lua* lpLua, const int data);
		/*
		//	Get this data type from the stack
		*/
		static const int	get(Lua* lpLua, int idx);
	};

	/*
	//	Unsigned Integer
	*/
	template<>
	class OBJECTLUA LuaDataType<const unsigned int>
	{
	public:
		typedef const unsigned int	ArgType;
		/*
		//	Push this data type onto the stack
		*/
		static void	push(Lua* lpLua, const unsigned int data);
		/*
		//	Get this data type from the stack
		*/
		static const unsigned int	get(Lua* lpLua, int idx);
	};


	/*
	//	Bool
	*/
	template<>
	class OBJECTLUA LuaDataType<const bool>
	{
	public:
		typedef const bool	ArgType;
		/*
		//	Push this data type onto the stack
		*/
		static void	push(Lua* lpLua, const bool data);
		/*
		//	Get this data type from the stack
		*/
		static const bool	get(Lua* lpLua, int idx);
	};


	/*
	//	Double
	*/
	template<>
	class OBJECTLUA LuaDataType<const double>
	{
	public:
		typedef const double	ArgType;
		/*
		//	Push this data type onto the stack
		*/
		static void	push(Lua* lpLua, const double data);
		/*
		//	Get this data type from the stack
		*/
		static const double	get(Lua* lpLua, int idx);
	};

	/*
	//	Float
	*/
	template<>
	class OBJECTLUA LuaDataType<const float>
	{
	public:
		typedef const float	ArgType;
		/*
		//	Push this data type onto the stack
		*/
		static void	push(Lua* lpLua, const float data);
		/*
		//	Get this data type from the stack
		*/
		static const float	get(Lua* lpLua, int idx);
	};


	/*
	//	const char*
	*/
	template<>
	class OBJECTLUA LuaDataType<const char*>
	{
	public:
		typedef const char*	ArgType;
		/*
		//	Push this data type onto the stack
		*/
		static void	push(Lua* lpLua, const char* data);
		/*
		//	Get this data type from the stack
		*/
		static const char*	get(Lua* lpLua, int idx);
	};

}

#endif